public class Main {
    public static void main(String[] args) {
      // 程序员在应用层里面, 最主要的工作是自定义协议
        // TCP/IP 的下四层 都是在系统内核/驱动程序/硬件中已经实现好的, 咱们只能去了解, 不能修改
        // 应用层协议, 则是咱们可以自定义的. (这个是咱们的主要了解对象)
        // 自定义协议要做两件事:
        // 1.明确协议数据要传递哪些信息.(根据需求来的)
        // 2. 明确数据的组织格式
        // 比如: 可以按照纯文本的方式 , 也可以使用 xml,json. protobuffer这样的格式

        // 应用层除了上述自定义的协议之外, 也有一些 大佬们已经设定好的, 现成的协议
        // 比如: HTTP 协议 (重要)

        // **************传输层*****************
        // 传输层虽然是操作系统操作系统内核实现好了, 但是程序员写代码,要调用系统提供的 socket api 完成网络编程, socket 就是属于传输层的部分
        // UDP
        // TCP
        // 端口号
        // (数据库 mysql 默认的端口号是 3306 )
        // (起到的效果就是区分一个主机上具体的应用程序的) , 所以就要求 , 在同一个主机上 一个端口号不能被多个进程绑定

        // TCP 和 UDP 协议报头中都会包含 源端口 和 目的端口
        // 这俩都是使用 2 个 字节, 16 个 比特位来表示的 (无符号的)
        // 一个端口号的取值范围 0 -> 65535
        // ( 1个字节 , 8bit 有符号就是: -128 -> +127  -128就是 -2^7, +127 就是 +2^7 - 1 ; 无符号就是 0 -> 255)
        // ( 2个字节 , 16bit 有符号就是 -32768 -> +32767 ; 无符号就是 0 -> 65535)
        // ( 4个字节 , 32bit 有符号就是 -21亿 -> +21亿 ; 无符号就是 0 -> 42亿9千万)
        // 但是咱们自己写的程序, 绑定的端口, 要从 1024 起的
        // 0 -> 1023 这个范围的端口, 称为 "知名端口 / 具名端口" , 这些端口号是属于已经分配给了一些知名的广泛使用的应用程序了.
        // 如果非要指定一个 0 -> 1023 之间的端口 你需要保证两个要求
        // 1.先确定你使用的这个端口确实没有程序在绑定
        // 2.确定你有管理员权限

        // **** UDP协议 ****
        // (比较简单)
        //  无连接 不可靠 面向数据报 全双工

        // *** UDP 的协议端格式 ***
        // 16位UDP长度，表示整个数据报（UDP首部+UDP数据）的最大长度；
        // 如果校验和出错，就会直接丢弃；

        //
        // 0___________________________15_16________________________________31_  --
        // |     16 位源端口号              |        16位目的端口号               |
        // -------------------------------------------------------------------   8个字节
        // |   16位置udp长度                |        16位UDP检验和               |
        // --------------------------------------------------------------------  --
        // |                                                                  |
        // |                                数据                               |
        // ____________________________________________________________________

        // 其实上面的呢个图是不够完整的, 但是是很多教科书上都标注的
        //
        // _____________________________________________________________________________________________
        // |   UDP报头     |        UDP载荷   (这里就是放的完整的应用层数据报)                                 |
        // ---------------------------------------------------------------------------------------------
        // (载荷就相当于 一辆货车后面车厢拉东西的地方)

        // UDP 就会把载荷数据(就是通过UDP socket,也就是 send 方法拿来的数据, 基础上再在前面拼装(此处就相当于字符串拼接,但是是二进制的不是文本的)上几个字节的报头)
        // UDP 报头里就包含了一些特定的属性, 就携带了一些重要的信息
        // 不同协议, 功能不同 , 报头中带有的属性也不同
        // 对于UDP来说, 报头一共就是 8 个字节,分成 4 个部分(每个部分2 个字节)
        // 2个字节表示的范围就是 64KB , 所以一个UDP数据报 最大只能传输 64KB 的数据
        // 如果应用层数据报 超过了 64 KB 怎么办?
        // 有两种解决办法
        // 1. 就需要在应用层,通过代码的方式针对应用层数据报进行手动分包,拆分成多个包通过多个 UDP 数据报进行传输
        // (本来send 一次, 现在需要send 多次了)
        // 2. 直接不用UDP了, 换成 TCP, TCP没有这样的限制

        // 校验和 作用是验证传输的数据是否是正确的
        // 网络传输的过程中, 可能会收到一些干扰, 在这些干扰下就可能出现 "比特反转" 的情况 ( 0 -> 1 ; 1 -> 0)
        // (网络传输 本质上就是 光信号/电信号 , 这些可能会受到一些物理环境的影响)
        // 这样的现象是客观存在的, 不可避免的. 咱们能做的就是 及时识别出 当前的数据是否存在问题 ~~ 因此就引入了 校验和来进行鉴别
        // (针对数据内容 进行一系列的数学运算, 得到一个比较短的结果(比如2个字节) , 如果数据内容一定,得到的校验的结果就一定,如果数据变了, 得到的校验和也就变了)
        // (发送方计算一边校验和, 接收方再重新计算一边校验和)
        // 但是也有一种可能存在就是 正确的传输数据的校验和 和 错误的传输数据的校验和 是一样的, 这种 在理论上是存在的, 工程上是极其微小的,忽略不计的
        // 如果内容相同,得到的校验和一定相同
        // 如果校验和相同,原始内容不一定相同 这是很少概率的

        //     UDP 报头                             |   UDP 载荷  (装的是应用层数据报)
        // _____________________________________________
        // | 源端口 | 目的端口 | UDP报文长度 | UDP校验和 |
        // -----------------------------------------------

        // 实际网络传输的过程中, 往往是把数据的所有字节都参与生成校验和运算,这样任何一个字节出现问题, 都能及时发现
        // 针对校验和的算法有很多种 :
        // 比较知名的有 :
        // 1.CRC : 循环冗余校验.简单粗暴,把数据的每个字节,循环往上累加.如果累加溢出了,高位就不要了, (特点就是 好算,但是校验效果不是特别理想)
        // 2.MD5 : 不是简单的相加,而是存在一系列公式来进行更复杂的数学运算(我们不多讨论) , (算法特点: 1.定长(无论你原始数据多长,得到的MD5值都是固定长度(有4个字节版本,8个字节版本...)
        //                              2. 冲突概率很小(原始数据哪怕只改动一个地方, 算出来的 MD5 值都会差别很大 (让 MD5 结果更分散了)))
        //                              3. 不可逆. (通过原始数据计算 MD5 很容易, 通过 MD5 还原成原始数据很难,理论上是不可实现的))

        //  MD5 这样的特点,就让 MD5作用更多了 : 1. 校验和 2. 作为 计算 hash 值的方式 3.加密
        // 3.SHA1 (这个的计算方式 和 MD5的方式是差不多的)


        // **** TCP 协议 ****
        // 协议特点 : 有连接  可靠传输  面向字节流  全双工
        //

        // 0-------------------------------15-16-------------------------------------31
        // |   16位端口号                       |       16位目的端口号                    |
        // ----------------------------------------------------------------------------
        // |                    32位序号                                               |
        // ----------------------------------------------------------------------------
        // |                    32位确认号                                              |
        // ----------------------------------------------------------------------------
        //     4位首部长度 | 保留(6位) | URG | ACK | PSH | RST | SYN | FIN | 16位窗口大小  |
        // ----------------------------------------------------------------------------
        // |               16位校验和                                    | 16位紧急指针   |
        // ----------------------------------------------------------------------------
        // |                                 选项                                      |
        // ------------------------------------------------------------------------------
        // |                                  数据                                      |
        // ------------------------------------------------------------------------------

        //  源/目的端口号：表示数据是从哪个进程来，到哪个进程去；
        //  32位序号/32位确认号：后面详细讲；
        //  4位TCP报头长度：表示该TCP头部有多少个32位bit（有多少个4字节）；所以TCP头部最大长度是
        // (TCP 报文 = TCP报头(首部) + TCP载荷) (TCP报头的长度是可变的 不是像 UDP一样 固定是八个字节)
        // 首部长度具体描述了TCP报头具体多长 , 另外, 选项之前的部分长度是固定长度 (20字节) (首部长度 - 20字节 就是 选项 部分的字节长度)
        // (此处的这个首部长度是 4 个比特位 表示范围就是 0 -> 15), 注意首部长度的单位 不是 字节 而是 4字节
        // 如果首部长度是 5 ,表示 整个TCP 报头是 20 字节 (相当于没有 选项)
        // 如果首部长度是 15 , 表示 是 60 字节
        //  15 * 4 = 60 (选项长度是 40 字节)

        // 保留(6位) : 是为了以后的拓展来考虑的.为啥要有保留位? (对于 网络协议来说 , 扩展升级是一件成本极其高的事情)
        // 如果引入 保留位 , 此时 升级操作的成本就会低不少
        // 如果后续 TCP 引入了一些新的功能, 就可以使用这些保留位字段, 此时 对于 TCP 本来的一个报头结构的影响是比较小的 , 老的设备即使不升级 也更容易兼容
        // (一定要考虑可扩展性)

        // ******6位标志位:******
        // URG：紧急指针是否有效
        // ACK：确认号是否有效
        // PSH：提示接收端应用程序立刻从TCP缓冲区把数据读走
        // RST：对方要求重新建立连接；我们把携带RST标识的称为复位报文段
        // SYN：请求建立连接；我们把携带SYN标识的称为同步报文段 (客户端主动给服务器发起建立连接请求,成为"SYN",同步报文段 (synchronize))
        // 这个比特为如果为1 就表示当前这个请求是一个同步报文段
        // 这时候如果 SYN 和 ACK 的比特位都为1, 那就说明这个 请求既是 同步报文段 也是一个 确认报文段
        // FIN：通知对方，本端要关闭了，我们称携带FIN标识的为结束报文段
        // 如果这个值为 1 就表示 结束报文
        // 16位窗口大小：后面再说
        // 16位校验和：发送端填充，CRC校验。接收端校验不通过，则认为数据有问题。此处的检验和不光
        // 包含TCP首部，也包含TCP数据部分。
        // 16位紧急指针：标识哪部分数据是紧急数据；
        // 40字节头部选项：暂时忽略； (选项(option)可选的,可有可无的), 此处的选项相当于 对 TCP报文的一些属性进行解释说明的


        // **** TCP 原理 ****
        // TCP 内部的工作机制
        // 当前主要讨论 TCP 提供的 10 个 比较核心的机制

        // 一.  确认应答（安全机制） :
        // TCP 的可靠传输 是怎么样做到可靠的? (可靠不是指数据百分之百发送给接收方 , 而是指 尽力而为~~同时 传输没传输成功 我们自己能不能知道 , 知道 就是可靠传输 反之就是 不可靠 )
        // 所以 确认应答 是实现 可靠传输 最核心的机制
        // A 给 B 发送消息 , B 收到之后就会返回一个 应答报文 (ACK) , 此时 A 收到应答之后, 就知道了刚才发的数据已经顺利到达 B 了 (这样的话 可靠性就建立起来了)
        // 上面这个是简单的情况, 稍微考虑复杂的 就会出现以下情况
        // 比如 A 给 B 连续发送两条消息
        // 但是 在网络中会有这样的情况出现  "后发先至" , 在这种情况下 收到消息的顺序是存在变数的 (每个数据报的传输速率是不一样的)
        //所以  应答报文的传输 速率 是不一样的 , 此时 就需要考虑如何规避这种顺序错乱所带来的歧义
        // 那么如何解决上述 后发先至的问题呢, 就是 给传输数据进行编号, 和应答报文都进行编号.
        // 即使顺序上乱了 我们也可以通过序号来区分当前应答报文是准对哪个数据进行了
        // 任何一条数据(包括应答报文)都是有序号的
        // 确认序号,则是只有应答报文有(普通报文确认序号字段里的值无意义)
        // 这一条报文是否是应答报文, 却决于 六个标志位 的第二条 ACK ,如果 这个标志位 为1, 表示是应答报文, 是0则不是应答报文
        // (应答报文的序号仅仅是一个身份标识)

        // (上面的都是简化模型, 实际上 TCP 的序号并不是 按照"一条两条"这样的方式来编号的... ,TCP 是面向字节流的, TCP 的序号也是按照字节来编号的)
        // 加入发一条数据是 1000 个字节的, 此时就是假设 从 1 开始编号,那么第二个字节序号就是 2
        // ..... 依次类推 编号, 但是这一千个字节都属于同一个报文, 所以 TCP 报头里就是记录当前的第一个字节序号
        // 此处报头的序号写的是 1
        // 那么此时再发第二条数据, 此时第二个TCP 数据报的头一个字节序号就是相当于 1001, 如果长度也是 1000 , 此时最后一个字节的序号就是 2000
        // 呢么由于这一段也是属于 同一个 TCP 数据报
        // 报头里只需要填写 1001 就行了

        // TCP 的字节的序号是依次累加的 , 这个依次累加的过程对于后一条数据来说,其实字节的数据来说 ,序号就是上一个字节最后一个字节序号的续写, TCP再写数据报的时候就需要写头一个字节学号就好了
        // TCP 知道了头一个字节的序号, 再根据TCP数据报的长度,就很容易知道每个字节的序号
        // 而确认序号的取值,是收到的数据的最后一个字节的序号 +1


        // 具体步骤: TCP将每个字节的数据都进行了编号。即为序列号。 每一个ACK都带有对应的确认序列号，意思是告诉发送者，我已经收到了哪些数据；下一次你从哪里开
        // 始发。

        // 小结: TCP 可靠传输的能力,最主要的就是通过 确认应答机制来保证的
        // 通过应答报文, 就可以让发送方清楚的知道传输是否成功
        // 进一步的引入了序号和确认序号, 针对多组数据进行详细的区分

        // 二. 超时重传（安全机制）
        // 讨论确认应答的时候, 只是讨论了顺利传输的情况
        // 那么如果丢包了呢
        // 丢包, 涉及到两种情况
        // 1.发的数据丢了  2. 返回的 ack 丢了
        // 发送方看到上述两种情况,就是没有收到 ack 区分不了是哪种情况
        // 所以这两种都是认为丢包

        // 而 丢包是一个概率性事件
        // (而且通常情况下 丢包的概率是比较小的, 因此 如果重新发一下这个数据报, 其实还是有很大的概率成功传输的!!)
        // 因此TCP 就引入了重传机制
        // 在丢包的时候,就要重新再发一次
        // (那么什么时候重传呢? 到底当前这次传输,是丢包了 还是 ack 走的慢 在路上呢?)
        // TCP 直接引入了一个 时间阈值.
        // 发送方放了一个数据之后, 就会 等待 ACK , 此时开始计时.
        // 如果在 时间阈值 之内, 也没有ACK 甭管此时ACK 是不是还是在路上,还是彻底丢了,都视为丢包
        // 超过一定时间,还没响应,就是重新传输
        // (时间阈值 是可以配置的 , 并且不同系统的默认值都存在差别)

        // 但是如果是ACK丢了, 这样会导致 数据重复收到了两次, 这样的重复收到有很大的风险
        // 所以 TCP 对于这种重复数据的传输,是有特殊处理的 去重
        // TCP 存在一个 "接收缓冲区" , 这样的存储空间(接收方操作系统内核里的一段内存),每个TCP 的 socket 对象 都有一个 接收缓冲区(其实也有一个发送区)

        // 主机B 收到了 主机A 的数据
        // 其实是 B 的网卡读到了数据,然后把这个数据放到 B 的对应 socket 的接收缓冲区中 (可以想象成一个阻塞队列(更严格的说可以理解成一个优先级队列)) (这个队列 可以 根据数据的序号 来识别 这两条数据是否是重复的, 如果重复则把后来的数据直接丢弃,
        // 保证了应用程序调用read读取的数据一定不重复的) (TCP 使用这个接收缓冲区,对收到的数据进行重新排序,使用read到的数据是保证有序的)
        // 后续应用程序使用 getInoutStream 进一步使用 read, 就是从接收缓冲区 来读取数据

        // 小结 : 由于去重和重新排序的机制的存在,发送方只要发现ACK没有按时到达,就会重传数据. 即使重复了,顺序乱了,接收方都能很好的处理好(去重和排序都依赖TCP报头的序号)

        // 但是 重传的数据也是有可能丢包的, 因此超时重传是可能重传N次的, 但是这个N并不是一个很大的数字, 重传这个事情,相当于重传几次都没传出去,此时继续重传意义已经不大了
        // 因此重传到一定此处,就不会继续重传, 会认为是网络故障
        // 接下来 TCP 会尝试 重置连接, 如果重置还是 失败 则会 彻底断开连接
        //重传的时候 第一次重传 和 第二次重传, 超时时间间隔 , 还不一样. 一般来说 重传的轮次越大, 超时时间间隔就越大

        // 小结: 可靠传输是TCP最核心的部分.
        //TCP 的可靠传输就是通过 确认应答 + 超时重传 来进行实现的
        // 其中确认应答描述是传输顺利的情况
        // 超时重传描述的是传输出现问题的情况
        // 这两者相互配合,共同支撑整体的 TCP 可靠性


        // 三. 连接管理 （安全机制）
        // 什么是连接 : (连接(connection) 跟 链接(link) 是有很大的差别的)
        //建立连接 就是 把 A 和 B 建立 连接 就是 准备 "一个东西" 上面都记录着这两个的 IP 和 端口 进而将两者连接起来
        // 链接 就像是一个 快捷方式  通过 这个快捷方式来进行程序的启动

        // 管理: 就是描述连接如何创建,如何断开.....
        //************* TCP 的建立连接过程(三次握手) 和 断开连接过程(四次挥手) (极其重要) ************

        // (JavaSE 这个 阶段最高频的面试题 : 谈谈啥是多态)
        // (数据结构这个阶段, 最高频的面试题: 讲讲什么是哈希表)
        // (数据库这个阶段, 最高频的面试题: 谈谈什么是事务)
        // (操作系统这个阶段, 最高频的面试题: 谈谈进程 和 线程的区别联系)
        // (网络这个阶段, 最高频的面试题: TCP 三次握手四次挥手)

        //  首先是建立连接 (三次握手)
        // 举一个例子: 如何确定连接建立完毕
        // 比如 A 和 B 表白
        // A 向 B 发送表白请求(说: B 你能做我唯一的哪个人么? ) 此时 B 也回应了 A (说: 好啊好啊)
        // 但是 想要成为真正的关系 必须 双发互为唯一
        // 只有上面的情况 只是 A B 都知道了 B 是 A 的 唯一 但是双方都不知道 A 是不是 B 的唯一
        // 所以还不是建立连接
        // 那么 这时候 B 也紧跟着发送了一条消息 (说 : A 你也能做我的哪个唯一么?) 这时候 A 也回应了 B (说: 好啊好啊)
        // 那么 此时也就说明 才是真正的建立了男女朋友关系
        // 这就相当于建立完毕

        // 那么上述 把这个过程中的 每一次通信 都称为是一次"握手" (这是一个形象的比喻)
        // 而在上面一共发送了四次消息,那么为什么是 三次"握手"呢?
        // 注意 上述的情况 有两条消息是可以合并的一次的
        // 那就是 B 对 A 说的全部 消息 呢两条可以合并成 一条消息

        // 小结 : 所谓三次握手,本质上是"四次"交互
        // 通信双方, 各自要向双方发起一个 "建立连接"的请求, 同时 再各自向对方回应一个 ack
        // 这里其实是一共有四次信息交互
        // 但是,中间的两次交互,是可以合并成一次交互的, 因此就构成了 "三次握手"

        // 为什么要把中间的合并? 不合并行不行? 一定要合并 !!!
        // (因为 封装分用, 通过 网络层,数据链路层,到最后的物理层,发送消息都是要经过层层封装的 , 最终才能 在网络上进行传输,
        // 接收方也是要经过层层分用,最终才能回到传输层 , 这里的封装分用是有开销的, 所以 你封装分用两次 一定比 封装分用一次成本要高很多的)
        // 少了最后一次 握手 , 是不能够建立连接的

        // *****三次握手的另外一个作用: 验证通信双方各自的发送能力和接收能力是否正常*****
        // (三次握手也是一定程度上保证了 TCP 传输的可靠性(起到的不是关键作用 而是辅助作用))
        // 举一个例子:
        // 比如 A 和 B 开黑打游戏
        // 需要开麦
        // A 先对 B 说一句话 (说: 好了么好了么)  (第一次交互)
        // (当B 听到 A 说的 好了么好了么 就证明 A 的麦克风是没问题 B自己的耳机是没问题的)  (而这时候 A 啥也不知道)
        // 这时候 B 给 A 回了一句 (OK了)  (第二次交互)
        // (当 A 听到 B 的回应 OK了 就证明B知道了 A 的麦克风没问题 , B 的耳机没问题, A 知道了 : A 的耳机是 OK的 , B的麦克风是 OK 的)
        // (由于之前的约定是, B 听到了 A 的 好了么好了么 再回复 OK了 , 这就证明 A 的麦克风没问题, B 的耳机没问题)
        // (这时候 B 不知道 自己的 麦克风有没有问题,也不知到 A 的耳机可以不可以)
        // 这时候 A 在给 B 回复一个 (好了) (第三次交互)
        // (这一次的回应 B 收到了 A 的回复 就证明了 两个的设备都没有问题 , 证明了: AB的设备都是好的)
        // 只有三次握手都顺利完成了才能证明 两台设备之后的操作能不能顺利完成

        // (确认应答和 连接 没有关系 , "有连接"的意思是: 1. 需要连接先建立好,才能进行通信. 2. 如果连接断开了, 此时无法继续通信了.
        // 3.连接建立过程中, 通信双方都要各自保存好对方的信息的.)
        // (所以 有没有连接,和 是否确认应答,没有任何关系!!, 确认应答体现的是"可靠传输" 可靠传输和有连接是没有关系的)
        // (无连接 , 也是可靠传输, 如 飞书/钉钉/企业微信.你发个消息不需要建立连接就能直接发,发的消息有个已读状态就相当于 ack)
        // (因为TCP是有连接的,所以TCP需要能够建立连接/断开连接,其中的的建立连接就是通过三次握手来完成的,  而 不是三次握手才体现连接的)

        // 三次握手的意义:
        // 1. 让通信双方各自建立对对方的"认同"
        // 2.验证通信双方各自的发送能力和接收能力是否可行
        // 3.在握手的过程中,双方来协商一些重要的参数.(什么是一些重要的参数: TCP 里面的编号, 就是 在TCP通信过程中,有些数据需要双方相互同步'
        // , 此时就需要有这样的交互过程, 恰好可以利用三次握手的机会,来完成数据的同步)
        // (前两个是最重要的)


        // *****(客户端永远是主动的一方, 服务器是被动的一方, 一定是客户端先向服务器发起"建立连接请求")*****
        // (客户端服务器这个身份概念,只是按照主动不主动来进行区分的. 同一个程序,在不同场景下可能是作为服务器也可能是作为客户端)
        // TCP 里面的 三次握手的 具体解释  ( A 是客户端 , B是 服务器)
        //   A  ------ 发送SYN-----------> B
        //   A <---------- 发送 SYN + ACK ----------- B
        //   A ----------- 发送ACK ------------> B

        // TCP的状态 需要记住几个常见的状态 ,(因为 TCP 的状态是非常复杂的)
        // 建立连接阶段 , 主要认识两个状态:
        // 1. LISTEN 这是 服务器的状态
        // 表示服务器已经准备就绪, 随时可以有客户端来建立连接了.
        // 2. ESTABLISHED 客户端和服务器都有的状态
        //  表示连接建立完成 , 接下来就可以正常通信了.

        // ***** (如果面试官问: 请你描述三次握手的过程? 此处建议 不要用嘴说 而是要画图 ,(线下面试为了充分体现要自己自带纸笔)) ****
        // (画图要画成啥样? 是否要花称上述课件的这个图的样子呢? 不需要!! 主要表示出基本流程就行了 (见上面的 具体操作 300 行)
        // ,画成上面那样都差不多了 , 并不一定需要 再搞其他东西 以免出错)


        // 接下来讲 ***************TCP 断开连接 . 四次挥手 *******************
        // "挥手" 和 "握手" 一样都是形象的叫法, 都是客户端 服务器之间的数据交互
        // 四次挥手 和 三次握手非常类似. 都是通信双方各自向对方发起的一个断开连接的请求, 再各自给对方一个回应
        // 上面介绍的是 表白的 过程 , 下面用分手的例子来举例
        //  A 向 B 提出分手
        // A 对 B (说: B 你已经不再是我的唯一了 .... (我要去找别人了))
        // 此时 B 回应 A (说:好啊)
        // 然后 B 又对 A 说了一句 (A,你也不再是我的唯一了)
        // 这时 A 对 B 做出回应 (说 : 好)
        // 此时就经历了四次挥手的过程, 彻底断开连接
        // 这一次就不能合并了么?
        // 注意 再断开连接的过程中 , 中间两次,通常情况下不能合并(特殊情况下可以),
        // (只有是 两个数据发送的时机相同, 才能合并. 如果是不同的时机,就不合并了)
        // 所以上述的三次握手 中间两次之所以能够合并 是因为 呢两个是同一时机
        // 具体来说 三次握手这三次交互过程,是纯内核中完成的,(应用程序感知不到 也干预不了)
        // 服务器的系统内核收到 syn之后,就会立即发送ack也会立即发送 syn

        // 而四次挥手则不是这样的
        // 具体描述 A 是客户端  B 是服务器
        // A   ----- 发送FIN ------> B
        // A   <-----发送ACK ------- B
        // A   <-----发送FIN ------- B
        // A   ------发送ACK ------> B
        // (FIN 的发起 , 不是由内核控制的, 而是由应用程序,调用 socket 的 close 方法(或者进程退出))
        // (才会触发 FIN , ACK 则是由 内核控制的, 是收到 FIN 之后, 就立即返回 ACK)
        // (第二个 FIN 是 服务器的应用程序,执行到对应的 close 方法,才会触发FIN, 所以 B 发送给 A 的呢两个消息之间隔了很长时间)
        // (隔的时间的多少取决于你写的代码)

        // "四次挥手"中涉及到的两个重要的TCP状态:
        // 1.CLOSE_WAIT (等待关闭, 等待调用 .close 方法 关闭 socket)
        // 出现在被动发起断开连接的一方
        // (建立连接一定是客户端主动发起请求, 断开连接,可能是客户端主动发起,也可能是服务器主动发起)
        // 2. TIME_WAIT (是要保持当前 TCP 连接状态不要立即就释放)
        // 出现在主动发起断开连接的一方
        // (假设客户端主动断开连接, 当客户端 进入 TIME_WAIT 状态的时候, 相当于四次挥手已经完了 (站在A 的角度看))
        // (那么为什要保持呢? 是因为 最后一个 ACK 只是刚刚发出去了, 但是还没有到对面呢, 以防止 最后一个 ACK 丢包)
        // (在三次握手 和 四次挥手 的过程中,同样是存在超时重传的. 如果是最后一个 ACK 丢包了, 站在服务器的角度来看
        // 服务器是不知道因为 ACK 丢了, 还是 自己发出去的 FIN 丢了,所以 统一视为 FIN 丢了, 统一进行重传操作
        // 既然服务器可能要重传 FIN , 客户端就需要能够针对这个重传的FIN进行ACK响应
        //  很明显, 如果刚才彻底把连接释放了, 这样的 ACK 就无法进行了. 因此使用 TIME_WAIT 状态保留一定的时间, 就是为了能够
        //  处理最后一个 ACK 丢包的情况, 能够在收到 重传的 FIN 之后 进行 ACK 响应)

        // 但是 TIME_WAIT 也不是 一直在等的
        // TIME_WAIT 会等. 如果等了一段时间之后, 也没有收到重传的 FIN 此时就认为, 最后一个 ACK 没丢,于是就彻底的释放连接了
        // 但是 此时 如果恰好最后一个 ACK 丢了, 又恰好服务器重传的 FIN 也丢了, 此时客户端等了半天,也没有收到重传的FIN就认为连接可以
        // 彻底释放了... (这种情况的概率很小, 但是如果发生了那也没有办法)
        // (TIME_WAIT 是发生在 已经两次挥手之后了.. 此时挥手就已经进行一半了,是不可逆的)

        // **** 那么到底是 TIME_WAIT 具体保持多长时间, 就能真正释放呢? *******
        // 约定一个时间 2MSL  (什么是MSL? 指的是 互联网上, 两个节点之间, 数据传输消耗的最大时间)
        // (通常情况下 MSL 这个值是 60s)
        // (那么为啥 要 2MSL ? 这也很简单 A 发送给 B ACK 需要一个 MSL 的时间, 但如果 要重传, 所以 B 给 A 再发一个 FIN 的时间也是 MSL
        // 这两个 MSL 之和就是最大等待时间)

        // 小结 :
        // TCP 作为一个有连接的协议 , 就需要建立连接和断开连接
        // 其中建立连接的过程是三次握手
        // 断开连接的过程是四次挥手
        //   这里的流程图要能话熟练
        // 重点理解 三次握手的意义:
        // 1.双方建立对对方的认同(保留对方的信息)
        // 2.验证通信双方的发送和接收能力
        // 3.协商一些关键参数

        // (关于三次握手:为啥不能是四次? 为啥不能是两次? 这里的关系也要搞清楚)
        // (关于四次挥手: 重点理解为啥当前的挥手是四次, 理解 FIN 和 ACK 的传输时机, 以及 TIME_WAIT 意义和作用)



        // 四. 滑动窗口.
        // 前面讲的呢三个 主要是给 TCP 可靠性提供的支持
        // (要清楚 引入了可靠性 其实付出了代价 那就是 传输效率)
        // TCP 竭尽可能的 提高传输效率.再怎么提高,也不可能比UDP效率高,起码也要让自己不要呢吗拉跨(本质上是补救措施)

        // 滑动窗口本质上就是降低了确认应答, 等待 ACK 消耗时间 (其实 再进行 IO 操作的时候 时间成本主要是两个部分, 1. 等 2. 数据传输(数据拷贝)
        // 大多数情况下 IO 花的时间成本大头都是在 等)

        // 那么如何降低等待ack 的消耗时间呢?
        // 那就是缩短时间, 具体怎么缩短?
        // 八个字 : 批量发送 , 批量等待. (把多份等待时间合并成一份)
        // 这就不得不考虑到前面的确认应答了
        // 对于 基本的确认应答来说, 每一次发一个数据 都需要等, 等 ACK 到了再发送下一个
        // 滑动窗口的本质 就是 不等待的批量发送一组数据, 然后使用一份时间来等待着一组数据的多个 ACK 了

        // ***   把不需要等待 就能直接发送的数据的最大的量, 称为"窗口大小"  ***
        // 当批量发送了窗口大小 这些数据之后, 发送方就要等待 ACK 了
        // 啥时候继续往下发送? 等待啥时候结束呢? 不是说,等待所有的 ACK 到达,才继续往下发, 而是到一个 ACK , 就继续往下发一条
        // (这要就保证 让此处等待的ACK 始终保持就是呢几条) (把这个过程形象的称为"滑动")
        // 数据肯定是紧挨着发的

        // 在 上述情况下 如果丢包了怎么办?
        // 还是按照两种情况来应对

        // 1. ACK 丢了
        // 这种情况 不需要做任何处理 没事
        // (关键要点 在于 确认序号的含义 , 表示 从该序号往前的所有数据都已经确认到达了)
        // (例如: 分段发送 : 1 ~ 1000 , 1001 ~ 2000 , 2001 ~ 3000 , 3001 ~ 4000 , 4001 ~ 5000 这 五个数据)
        // (如果 1001这个ACK 丢了 而 2001这个 ACK 到了 就表示 2001 之前的数据都已经确认到达. 因为 2001 之前 涵盖了 上一个 1001 这个 ACK 信息 )
        // (任何一个ACK 都能涵盖前面任何一个数据) (实际上这里的 ACK 并不是 老老实实的全部在发的. 可能会故意少发一部分, 并不影响可靠性, 同时节省系统资源)
        // (如果 所有 ACK 都丢完了那就是极端的错误了,说明网络已经严重故障)

        // 2.数据丢了
        // 这个时候我们就需要进行重传了
        // (例如: 分段发送 : 1 ~ 1000 , 1001 ~ 2000 , 2001 ~ 3000 , 3001 ~ 4000 , 4001 ~ 5000 这 五个数据)
        // (如果 1 ~ 1000 传进去了 , 1001 ~ 2000 没有传进去 , 而 2001 ~ 3000 也传进去了)
        // (这时候 第一个ACK 1000已经正常返回 , 但是第二个 ACK 返回的是第三个数据发送过来的, 但是这个序号还是按照 ACK 的顺序来定 是 1001 发送的 )
        // (由于 1001 ~ 2000 丢包 接下来 2001 ~ 3000 到达主机 B 之后, B 给 A 返回的 ACK 确认序号仍然是 1001 (此时和你发来的这个数据序号是啥,关系不大了)
        // 意思是 在索要 1001 开头的数据 . 接下来发送一个 3001 ~ 4000 此时主机B 还是返回一个 1001 , 又发送来一个 4001 ~ 5000,
        // 此时主机B 又是返回一个 1001, 所以 只要 1001 ~ 2000 这个数据不发送到 就会一直索要 这个数据)
        // (接下来 A 就会意识到 不对劲 , 说明 1001 B 是没有收到的, 就需要对 1001 进行重传了~~ )
        // (这时候 重传之后 B 收到了 1001 的数据 , 所以接下来就会索要 5001 的数据 , 因为前面的 数据都已经被 B 收到了)(相当于最后一块拼图拼上了)
        // (但是如果中间的还有一组数据丢包了, 那只有等待 第一次丢包的数据重传回来之后 再继续索要第二次丢包的数据)
        // (别忘了 TCP 有一个接收缓冲区 , 会在缓冲区里按照序号重新排队的)
        // 上述 丢包重传的方式 起了个名字叫做 "快速重传" (重传操作 只重传了丢失的数据)
        // (这个 快速重传 也可以视为 超时重传的机制 , 在滑动窗口下变形的 , 如果当前传输数据密集, 按照滑动窗口的方式来传输,此时按照快速重传来处理丢包
        // 如果当前传输数据稀疏, 不再按照滑动窗口的方式了, 此时还是按照之前的超时重传处理的)


        // 五. 流量控制
        // 这是一种干预发送的窗口大小的机制
        // 滑动窗口, 窗口越大 , 传输效率越高 (一份时间, 等 ACK 就越多)
        // 但是 也不能无限大, 因此这个滑动窗口也是要进行限制\
        // 1. 完全不等 ACK , 可靠性能否保障画上问号
        // 2.窗口太大, 也会消耗大量的系统资源
        // 3. 发送速度太快, 接收方处理不过来, 发来也白发
        //  接收方的处理能力, 就是一个很重要的约束依据
        // 发送方发的速度,不能超出接收方的处理能力
        // 所以流量控制要做的工作就是这个. 根据接收方的处理能力, 协调发送方的放松速率.

        // 所以 如何来衡量接收方的处理能力?
        // 一个量化的方法: 计算接收方一秒钟能处理多少个字节....(这种方式,实现起来有些麻烦)
        // 因此会使用一个更简单的方法
        // 直接看接收方接收缓冲区的剩余大小
        // 剩余空间剩余的越大就表示处理元素的速度越快,省的空间越小就证明处理元素的素的越慢,所以要调低一些发送方的发送速度
        // 每次 A 给 B 发了一个数据
        // B 就需要计算一下处理空间的剩余空间的大小, 然后再把这个值通过 ACK 报文返回给 A
        // A 就根据这个值来决定接下来发送的速率是多少 ,(窗口大小是多少) // TCP 报文字段里面有一个 16位窗口大小 , 这个只有在 报文是ACK的时候才有效
        // 发送方就会根据这个数字来确定下一轮发送的窗口大小

        // 而这里面的窗口大小 是否是 最大 64kb呢? 不是
        // TCP 为了让窗口更大, 在选项部分,引入了窗口扩展因子
        // 会通过这个因子进行左移
        // 比如当达到 64kb 的时候 扩展因子里面就会写一个2 意思就是 让 64kb << 2 => 256kb (2倍)
        // 发送方窗口大小不是固定值,也不是配置的, 而是随着传输过程进行 动态调整的

        // 主机 A 给  主机B发送报文的时候
        // B 会有一个窗口报文
        //  当窗口大小为0的时候 (说明之前发送的数据并不是立即处理的,而是先放在一个缓冲区中), 发送方就要暂停发送
        // 暂停发送的等待过程中, 会给 B 定期发送 窗口 探测报文, 这个报文不携带具体的业务数据, 只是为了触发 ACK  , 查询窗口大小


        // 六. 拥塞控制
        // 流量控制 和 拥塞控制共同决定发送放窗口大小是多少 (发送方按照滑动窗口的方式发送,此时"窗口大小"描述了发送速率)
        // (窗口大小 是 发送方的概念, 只不过这个窗口的大小 是通过接收方的 ACK 报头里的 窗口大小字段,从接收方告诉发送方)
        // (流量控制 考虑的是接收方的处理能力)

        // (拥塞控制 描述的数传输过程中中间节点的处理能力(中间的交换机 和 路由器的处理能力)) (木桶效应)
        // 接收方 处理能力好量化衡量的
        // 但是中间节点不好衡量(因为你根本就不知道中间节点有多少个路由器和交换器)
        // 所以就想出了一个相当天才的方法
        // 既然不好量化, 那就通过实验的方式 来测出一个合适的值
        // (把中间的这几个节点视为一个整体, 来一点一点的测试)
        // (拥塞控制 本质上就是通过这样的实验方式,阿里逐渐找到一个合适的窗口大小(合适的发送速率))
        // 有一个实验表 初始阶段,由于初始窗口比较小,每一轮不丢包都会使窗口大小扩大一倍(指数增长)
        // 但是会设定一个阈值, 当增长速率达到一个阈值之后,此时指数增长就成了线性增长 (增长的前提是不丢包)
        // 再接下来,当传输过程中一旦丢包了, 说明此时发送的速率已经接近网络的极限了
        // 此时就把窗口大小一下缩成了一个很小的值(重复刚才的指数增长和线性增长的过程)
        //新的阈值是刚才 在触发丢包之后 除二得到的
        // 拥塞窗口不是固定数值, 而是一直动态变化的
        // 随着时间的推移, 逐渐达到一个动态平衡的过程
        // 这样做既能把问题解决掉, 同时也能随着网络的动态变化而变化

        // 拥塞窗口 和 流量控制的窗口, 共同决定了发送方实际的发送窗口
        // (拥塞窗口和流量控制窗口的较小值)

        // 七. 延时应答
        // 也是提升效率的机制
        // 也是在滑动窗口的基础之上,搞事情
        // 滑动窗口的关键,让窗口大小大一点~~~ 传输速度就快一点
        // 因此 要做的是 在接收方能够处理得了的前提下,尽可能得把窗口大小放大一点
        // 延时.收到数据之后,不是立即返回 ACK 了 而是稍微等会再返回
        // 等待得时间里,接收方得应用程序,就能够把接收缓冲区得数据给消费一波
        // 此时就会有更大得一个空间, 就达到了一种类似于"超频" 得效果
        // 实际上 延时应答采取得方式,就是在滑动窗口下, ack 不再每一条数据都返回了,比如隔一条返回一个ACK 或者其他
        // 实际上 剩余空间大小得变化是一个复杂得过程,既取决于发送方得发送,也取决于接收方得处理

        // 八. 捎带应答.
        // 也是提高传输效率得一个方式, 在延时应答得基础之上 引入得捎带应答
        // 服务器客户端程序 最典型得模型 就是 "一问一答".
        // 比如 A 要 问 B 现在是几点了 , 会有一下步骤
        // A ---------- 现在几点了? --------------> B
        // A <--------- 我看看 ------------------- B
        // A <---------- 12:08 ------------------ B
        // A ----------- 我知道了 ----------------> B
        // 其中呢 这里面得 (我看看 , 和 我知道了 其实就是 ACK , 是内核立即返回得)
        // 而(现在几点了 和 12:08 这个是业务上得响应. B 应用程序里发送得)
        // 这俩本身是不同得时机 , 但是 TCP 存在 延时应答
        // 由于上面得延时应答机制, 就导致等待 ACK 得过程中, B 就要给 A 发送业务数据了, 这样就可以 让业务数据 稍上这个 ACK 一起发送出去就行了
        // 这样就变成了
        // A ---------- 现在几点了? --------------> B
        // A <--------- 我看看 12:08  ------------ B
        // A ----------- 我知道了 ----------------> B
        // 这样 本来是不同时机,在延时应答下,可能成为了相同时机,就合并了, TCP 三次握手本身就是相同时机 , 这两个本质上还是不太一样
        // TCP 是 100% 合并的, 而此处是有一定概率 合并的 , 这里和四次挥手的合并更像
        // (捎带应答本身就是在说能合并这个事情, 延时应答是提高了合并概率)

        // 九.面向字节流
        // 面向字节流 引入了一个麻烦事 , 粘包问题
        // 因为 接收缓冲区, 其实就是把多个收到的数据都放到一起了~~
        // 应用程序 read 读取的时候 , 读到哪里才算是一个完整的应用层数据报呢??
        // 由于 TCP 是字节流的
        // 一次读1个字节 , 读 N 个字节都是可以的
        // 这就导致一次读到的数据. 可能是半个应用层数据报, 可能是一个应用层数据报, 也有可能是多个应用层数据报~~ (就如同东北粘豆包的情况,很难加起来一整块)
        // (这个现象就成为 "粘包问题" (有的人读 nian(二声) 有的人 读 zhan (一声)))

        // 当有多个应用层数据报的时候 , 应用程序调用read , 当read 读到的字节数不同时, 有可能会读 一个完整的数据报,也有可能读到一个半的数据报,也有可能读半个数据报
        // 在 TCP 层次, 没有在 socket api 中告诉我们应该读取几个字节
        // 具体怎么读, 完全是程序员自己负责 ,(让自己负责就很容易搞晕)
        // 毕竟, 期望读到的是整个的应用层数据报,后续才好处理
        // 接下来要注意 如何解决这样的粘包问题?
        // 解决方法很简单, 在应用层, 约定好应用层协议 即可. 尤其是明确应用层数据报和应用程数据报之间的边界就好了. (报和报之间的边界)
        // (在传输层看是不同的报, 而应用层则看的是一个完整的字节)
        // (所以用分隔符)
        // 两种解决方法: (二选一)
        // 1. 约定好分割符 (分割符必须是包中不可能存在的,选一个在数据中完全不会用到的)
        // 2. 约定每个包的长度
        // (要理解 read 的工作特点)

        // 十.异常情况
        // 传输过程中出现了不可抗力
        // 如 : 1. 进程崩溃了. 2.主机关机了(这是正常流程关机) 3.主机掉电了(拔电源) 4. 网线断开
        // (1,2 : 属于 进程没了, 对应的 PCB 就没了, 对应的文件描述符表就释放了. 相当于 socket.close()
        //  此时内核会继续完成四次挥手.此时其实仍然是一个正常断开的流程 .
        //  主机关机要先杀进程,让才正式关机,(杀死进程的过程中, 也就是和上面一样触发四次挥手))

        // (3,4 : 这两个情况显然是来不及挥手了
        // 假设是接收方掉电了 , 发送方仍然在继续发数据, 发送完数据要 等待 ACK . ACK 等不到....
        // 就要进到超时重传, 再怎么重传 也收不到 ACK , 重传几次还没发现 ACK 就尝试重置TCP连接 (复位报文段..RST).
        // 显然这个重置也会失败,这时候就会放弃连接.(单方面放弃了)
        // 那如果是发送方掉点了呢??
        // 接收方发现, 没数据了. 没数据是发送方挂了? 还是发送方要组织下语言.稍等会再发?? 接收方不知道
        // 只能先等. 等了一段时间之后, 接收方需要周期性的给发送方发送一个消息,确认一下对方是否还工作正常.(这个消息不携带任何数据,称为"心跳包")
        // (心跳包也是一个形象的比喻: 1. 心跳是周期性的 2. 如果无心跳,说明就挂了) , 通过心跳包来确认通信双方是处于正常工作状态, 保活机制)
        // (网线断开 是双方都掉线了, 各自走各自的机制)
        // (心跳包是一个非常常见的机制)

        // TCP 是个非常复杂的协议!!! 不仅仅是这个十个特性. 这十个是比较核心的特性
        // 想要了解更多就翻阅 RFC标准文档 或者 翻阅操作系统内核源码 (其他的文章帖子慎重参考)

        // UDP 和 TCP 的对比
        // TCP 的优势在于 可靠传输 (绝大部分场景中都需要可靠传输)
        // UDP 优势在于 能够保证一个更高的效率. 如果有些场景对于性能要求更苛刻.(同一个机房内部的(网络结构简单,网络带宽比较充裕,转发设备也是比较好的设备),
        // 服务器之间通信 (整体丢包的可能性比较小))
        // UDP 还有一个小优势: 天然支持 广播 (比如收音机呢个收到的广播信号,这个信号不管有几个设备都能收到这个信号)
        // IP地址中有一种特殊的地址 广播 IP 通过 UDP 往广播IO 上发送数据报,此时该局域网内所有的设备都能收到数据
        // (如投屏要求, 手机和电视 再同一个 wifi 下, 同一个局域网下, 手机把当前播放的视频地址 发送到电视上. 因此手机就要知道电视的IP地址
        // 进行投屏的时候, 手机就需要现在当前局域网内广播,"找找电视",然后手机这里就能看到一个投屏的设备列表) TCP 就不支持这样的操作, 除非再应用层写代码进行这样的功能

        // 传输层的协议并非只有这两个.  有一个专门为游戏场景来量身打造的
        // 如: 一个典型的协议: KCP , 以KCP为代表的一系列协议

        // ************网络层************
        // 要做的只有 : 1. 地址管理   2. 路由选择
        // (快递系统要想建立起来,就需要先把世界上的地址能够以一定的规范定义出来)
        // 代表协议: IP协议
        // TCP/IP 协议栈 : TCP+IP
        // 接下来 看看 IP协议的报头格式
        // IPv4 协议(v4是版本的意思)
        // 0------------------------------------15|16-----------------31  ---
        // | 4位版本 | 4位首部长度 | 8位服务器类型(TOS) |   16位总长度(字节数)  |   |
        // -------------------------------------------------------------   |
        // |                16位标识                | 3位标志 | 13位片偏移 |    |
        // -------------------------------------------------------------
        // | 8位生存时间(TTL)     | 8位协议           |  16位首部校验和      |   20 字节
        // -------------------------------------------------------------
        // |                          32为源 IP地址                      |   |
        // -------------------------------------------------------------   |
        // |                         32为目的的IP地址                     |   |
        // -------------------------------------------------------------   ---
        // |                          选项(如果有)                        |
        // --------------------------------------------------------------
        // |                         数据                               |
        // --------------------------------------------------------------

        // 4位版本 此处的取值只有两个: 4 和 6  (表示当前版本)   (当前主要介绍 IPv4)
        // 4位首部长度  描述了IP报头多长 (IP报头是边长的) (IP 报头中有一个选项部分,是变长的,是可有可无的, 此处的单位也是4字节)
        // 8位服务器类型. 说是 8 位 实际上只有4位有效. 这4位其中只有 1位 可以是 1, 其他都是 0. 4位就表示 IP协议的四种形态/ 四种工作模式
        // (这四种形态分别有 : 最小延时 , 最大吞吐量 , 最高可靠性(这个不是TCP上的呢个可靠性), 最小成本 (四个每次只能用其中一个))
        // (在实际开发中就可以根据需要 来切换 IP 模式,达到最优效果 ~~ , 然而实际开发中很少真的这样来设定 , 主要因为上面呢几个东西都是站在技术角度进行处理的
        //  实际上技术上的东西一般都好办, 难办的都是业务上的处理)

        // 16 位 总长度 : 描述了 一个 IP 数据包的长度  (头 + 载荷 的 总长度)
        // (这个长度 减去 前面的IP报头长度, 剩下的就是载荷长度, 一个完整的 TCP / UDP 数据报长度)
        // 16位总长度, 是否就是意味着一个 IP 数据报, 最大只能支持 64 KB呢?
        // 确实是有这个限制的, 但是 IP 自身就支持对 包的拆分和组装 ....
        // 一个 IP 数据报携带的数据载荷太长了 超过了 64KB , 就会再网络层针对数据进行拆分, 把一个数据拆分成多个 IP 数据报
        // 再分别发送, 接收方 再重新拼装

        //-------------------------------------------------------------
        // |                16位标识                | 3位标志 | 13位片偏移 |     // 这几个字段都是辅助拆包/组包提供的
        // -------------------------------------------------------------
        // 16位标识 : 同一个数据拆成的多个包,标识是一样的
        // 13位片偏移 : 标识了多个包的先后顺序
        // 3位标志: 标识结束标志位

        // 8位生存时间 : 表示一个数据报在网络上能够传输的最大时间 , 这个时间单位为:次数 (一个数据报构造出来, 会有一个初始的 TTL数值 ,
        // 这个报每次经过一个路由器转发 TTL就会 减一,
        //  如果一直减到0了,还没有到达目标,此时就认为这个包永远也到不了了, 就可以丢弃了)
        // 8位协议 : ,描述了当前载荷部分是属于哪一个协议的 (TCP / UDP)
        // 16位首部校验和 : 此处只需要针对首部进行校验 , 载荷部分(TCP / UDP数据报) 自身已经有校验和了 (IP本身不涉及重传. 如果上层使用TCP, TCP会在没有收到ACK之后就重传)

        // **** 32位源IP地址 和 32位目的IP地址 (重要) ****
        // 此处 看到的 IP地址 是 31位整数 (而日常所见到的则是一串数字)
        // 使用 三个 . 把 32位(4个字节)的数字给分割开, 分成 4 个部分.
        // 每个部分分别使用 0 - 255 十进制整数表示
        // (称作 点分十进制) (这个方法目的是为了给人看的 而不是 给机器看的)
        // 地址,期望每个设备都不相同. 但是 32 位数字 只能表示 42亿9千万个数字.... (期望是使用这个表示全世界所有的上网设备, 智能手机 + PC + 服务器 + 物联网(智能冰箱/洗衣机)+ 杂七杂八的网络设备)
        // 这些显然是不够用的 , 如何解决的?
        // 1. 动态分配 IP 地址. (谁要上网就给谁分配IP地址,谁不上网就不分配, 此时就可以剩下一些IP地址了) (但是这个方案指标不治本, 只是提高了利用率)
        // 2. NAT网络地址转换 (本质上是使用一个IP代表一批设备. (此时也就能够大大提高IP地址利用率),再使用端口号来具体区分)
        // (在NAT背景下, 就把 IP 地址分成两大类. 1. 内网IP(私有IP) () 2.外网IP(公网IP))
        // (内网 IP 一共有三种 : 10*   172.16.* - 172.31.*   192.168.* 是由这三个开头的都称作是内网IP
        //  那么剩下的所有 IP 都称作是 外网IP, NAT要求 , 公网IP 必须是唯一的 , 私网IP可以在不同的局域网中重复出现~~)
        // 如果某个私网里的设备想要访问公网设备,就需要对应的NAT设备(路由器), 把IP地址进行映射, 从而完成网络访问
        // 反之公网的设备,无法直接访问私网的设备,不同局域网的私网的设备没法直接相互访问
        // (私网IP 只要是 经过运营商路由器转发给服务器,服务器看到的源IP地址都一样, 如果是多个电脑同时访问一个服务器,服务器的响应就会先发给路由器
        // ,路由器根据这些电脑不同的端口号来区分,决定发给哪个设备)

        // NAT机制能够有效解决IP不够用的问题,但是带来的副作用就是网络环境更加复杂了

        // 3.IPv6[根本上解决了 IP不够用的问题]
        // 使用 16 字节, 表示 ip地址
        // 16字节表示的地址的个数,是比 4 字节表示的个数 大 4倍 么?
        // 不是 , 而是指数关系,而是4次方
        //但是当前世界仍然是以 NAT+IPv4 + 动态分配来组件网络的, 真正使用IPv6的地方非常少
        // 为什么? 一个字 贵! , IPv4 和 IPv6 不兼容
        // 当前用 IPv6 地方最多的就是 CHINA !!!!!
        // 为的就是与美国针对(防止掐脖子) , IPv6 已经做好了真正的准备, 但是还没有正式开启, 当美国真正搞事的时候,可以随时一声令下可以切换到IPv6
        // 有一些设备是可以把IPv6 和 IPv4 相互转换的

        // IP地址格式
        //IP地址是一个32位的二进制数，通常被分割为4个“8位二进制数”（也就是4个字节），如：
        //01100100.00000100.00000101.00000110。
        //通常用“点分十进制”的方式来表示，即 a.b.c.d 的形式（a,b,c,d都是0~255之间的十进制整数）。如：
        //100.4.5.6。

        //组成
        //  IP地址分为两个部分，网络号和主机号
        //  网络号：标识网段，保证相互连接的两个网段具有不同的标识；
        //  主机号：标识主机，同一网段内，主机之间具有相同的网络号，但是必须有不同的主机号；
        //  通过合理设置网络号和主机号，就可以保证在相互连接的网络中，每台主机的IP地址都是唯一的。
        // 如: 192.168.0.10
        // 网络号是 192.168.0
        // 主机号是 10
        // 当前的局域网下的设备,网络号都是 198.168.0 , 但是主机号是不同的, 如果同一局域网下有两台设备的主机号一样那必有一个不能上网
        // 任何路由器都是有两个口的 一个 WAN 口 一个 是 LAN口
        // 一个口 是 WAN口是用来差光猫的 , 另外四个是 LAN 口 , 每一个口都是连接不同的局域网
        // 光猫同时也是一个路由器, 也有 WAN口 和 LAN 口 , 光猫的 LAN口是和主机的路由器上面的 WAN口连接的
        // 所以家里面的路由器是在两个局域网里面 , 两个相邻的局域网的网络号是不能相同的

        // IP地址从哪到哪是网络号 , 后面的部分从哪到哪是主机号? 怎么区分
        // 其实有一个单独的概念, 子网掩码~~
        // 用来描述网络号是什么样的范围(网络号不一定是前三个字节,都是可以调整变化的)
        // 子网掩码也是一个 32 位的整数, 子网掩码有一个特点, 左侧都是 1 , 右侧都是 0
        // 1 的部分就是描述了 IP有多少位是 网络号
        // (1的多少位都是可以通过组网来设计的)
        // (家用的子网掩码最常见的是 255.255.255.0)

        // IP地址的分类: A类 ,B类 , C类 , D类 (这些都是 在有子网掩码之前,采用的网络号划分的方式(换句话说之前的网络号是固定死的,
        // 但是有了子网掩码之后这个东西就不用了)) (这里我们不做过多强调,但是学校的考试要用)

        // 特殊IP地址
        //将IP地址中的主机地址全部设为0，就成为了网络号，代表这个局域网；
        //将IP地址中的主机地址全部设为1，就成为了广播地址(如果UDP往这个地址上发数据报,整个局域网的所有设备都能收到)，用于给同一个链路中相互连接的所有主机发送数据包；
        //127.*的IP地址用于本机环回（loop back）测试，通常是127.0.0.1
        //本机环回主要用于本机到本机的网络通信（系统内部为了性能，不会走网络的方式传输），
        //对于开发网络通信的程序（即网络编程）而言，常见的开发方式都是本机到本机的网络通信。

        // 主机号位1的,比如 192.168.0.1 通常是 "网关IP" (不是绝对的,都是可以配置的)
        // (网关就是 , 网络的入口 和 出口 (一夫当关 万夫莫开, 关就是入口的意思))

        // IP地址还有一个重要的功能 路由选择 ,但是面试不考
        // 路由选择就是规划路径
        // 路由选择, 核心思路, 问路!!
        // 每个路由器都会保存一定的周围设备的信息(路由表)
        // 每次有一个IP数据报经过路由器,就需要匹配路由表,看着接下来怎么走
        // 如果路由器上有匹配表项 , 直接按照要求的走就行了
        // 如果没有匹配选项 , 就会提供一个默认的路径, 大体方向是 不会错的....沿着这个方向继续走, 继续问
        // 每次经过一个路由器问一次 TTL -1
        // 如果减到0了, 还没有到, 说明这个报就永远也到不了了,就被丢弃了
        // 正常情况下TTL是足够用的 , 32这样的TTL足以让我们把数据报转发给世界上的任何一个地址  (六度空间理论), (也有 64 和 128 这样的)
        // (路由选择简单来讲是这样的, 具体的还是很复杂的,不做过多讨论)


        // ********** 数据链路层 ************
        // 考虑相邻两个节点之间的传输
        // (通过网线 / 光纤 / 无线直接相连的两个设备)
        // 这里的典型协议有很多, 其中最知名的就是 "以太网"
        // 这个协议其实规定了数据链路层 也规定物理层的内容
        // 咱们平常使用的 网线, 就叫做 "以太网线" (遵守以太网协议的网线)

        // 以太网数据帧 格式:
        // --------------------------------------------------------
        // | 目的地址 | 源地址 | 类型 |            数据          | CRC |
        // --------------------------------------------------------
        //    6字节     6字节  2字节        46 - 1500 字节        4字节
        // 一个以太网数据帧 = 帧头 + 载荷 + 帧尾 (载荷就是一个完整的 IP 数据报)
        //                  (帧头就是呢三个)
        //                  (里面的 目的地址 和 源地址 不是使用IP地址表示 而是搞了一个 MAC地址 (也称作 物理地址 (完全是另外一套地址体系)))
        // 这个地址是六个字节 当前每个设备都有唯一的MAC地址 , 不是动态分配的,而是网卡出厂的时候就被设置好的
        // 既然都有了IP地址 为什么 还要又 MAC地址?
        // 假设只有一套地址体系(比如 IP 和 以太网都使用 IP地址 或者 MAC 地址) , 完全是可以够用的
        // 但是 当初 大佬们研发初代的协议栈的时候,研发网络层协议 的 大佬 和 研发 数据链路层协议的大佬是两伙人...
        // 各自独立研发出了一套地址体系..
        // 干脆, 各用各的就行了 .. 于是当前就演化成了两套地址体系 相互配合的现状..

        // 当下的 IP 和 MAC 是如何相互配合的呢?
        // 各司其职
        // IP 用来描述整个传输过程的起点和重点 MAC 则是用来描述 两个相邻节点的 起点和终点
        // 数据链路层是考虑两个相邻节点之间怎么走 (相当于 IP 描述的是 初心, 最初从哪来到哪去, 最初的目标是啥. MAC描述的是 当前阶段的 任务)
        // 如果使用同一套地址体系,能否完成这个过程呢? 完全可以

        // 而类型部分有三种 : 0800  0806   0835 这三个
        // 0800 是普通的以太网数据帧. 载荷部分就是一个完整的IP数据报
        // 0806 载荷部分是一个 ARP 报文
        // 0835 载荷波分hi一个 RARP 报文
        //(咱们数据链路层中另外一个协议. ARP 协议 通过这个协议 让某个路由器 / 交换机 能够认识局域网里的其他设备 , 通过这个 会在 交换机 / 路由器
        // 里建立一个表 (这个表相当于一个 hash 表) 能够建立出 IP 和 MAC 之间的映射关系 )

        // ***认识 MTU ***
        // MTU 是一个数据链路层的数据帧, 能够承载数据的最大长度
        // 载荷具体多长 和 使用的物理介质有很大关系, 当然也和数据链路层使用的协议有很大关系
        // 正是这个 MTU 引起了 IP 这个协议来进行分包组包
        // IP 的分包组包通常不是根据这个IP最大长度 64k来份的.. 大概率情况下 数据链路层的 MTU都比64kb要小

        // ***** 还要补充一个协议 *****
        // DNS 协议 (是一个应用层上的协议)
        // 也是当前互联网的基石

        // DNS 是一个域名解析系统

        // 什么是域名? 网址 ~~~  如 www.baidu.com
        // 其实网络上的服务器, 要访问它,需要的是 IP地址
        // 但是IP地址太拗口,不好记.. 于是使用一些简单的单词构成字符串来表达这个地址
        // 每一个域名都对应了一个或者多个 IP地址
        // 既然如此, 就需要把 域名 和 IP地址给对应上

        // 最原始的做法是 使用一个 hosts 这个文件
        // 这个文件就像一个 hash 表一样 建立了 IP 和 域名的映射关系
        // 这是最早的DNS系统
        // 当网络多了 , 就服务不过来了
        // 所以就搞了一个 DNS 服务器
        // 当你访问某个域名的时候, 就自动请求一下 DNS  服务器, DNS 服务器就帮你进行查询, 把得到的结果返回给你(结果就是具体的IP地址)
        // 如果你电脑的 DNS 服务器配置不对,或者 DNS 服务器挂了,那么你就打开不了网页了

        //当前要求网站的域名不能重复(保证唯一), 全世界这么多网站如何保证唯一呢?
        // 就针对域名进行分级
        // 有: 一级域名 , 二级域名 , 三级域名
        //  如: pic.sougou.com
        // .com 是 一级域名 ,com就是"公司"的缩写, 类似的一级域名还有 org , cn , us...
        // .sougou 就是二级域名,表示搜狗这个公司
        // pic 是三级域名
        // 域名分级了 DNS服务器也就分级了
        // (所以只要有其中一级不重复就可以了)
        // 查询DNS服务器的时候也是分级来查询的 , DNS服务器也是分为 一级域名服务器 二级域名服务器..
        // www 是 "万维网"的缩写
        // 你现在要想注册一个网站, 申请一个域名, 把你的域名和服务器 IP 地址关联起来 --> 把你的数据写道 DNS服务器里..
        // 全世界有千千万万的DNS服务器...但是这里的这些服务器的数据都是来自哪里?
        // 根域名服务器... 根域名服务器主要是由美国人掌握的
        // 除了根域名服务器之外, 其他的都是"镜像服务器" 数据要从根域名这里同步的~~

        // DHCP 是组网的时候搞得一个自动分配 IP地址的协议 , 组网的时候, 局域网里的每个设备 IP都是可以手动配置的,
        // 但是手动配置太麻烦了, 可以自动分配,所以大佬们就研究了 DHCP协议, (咱们电脑上的设置 自动获取 IP 地址, 此时就是基于DHCP,
        // 让路由器来给咱们的电脑安排个 IP )

        // 这个网络原理的章节 还涉及到一个非常经典的面试题:
        // "在 浏览器里输入 www.baidu.com 之后, 按下回车, 最终展示出的网页,这个过程都发生了哪些事情?" 这里的问题要自行研究
        // 如果遇到了这个问题 回答应该不少于三十分钟


    }
}